/*
 * Widget
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

#include <algorithm>
#include "Widget.h"
#include "misc.h"
#include "dirtyrect.h"

namespace animationagent {

void Widget::init()
{
    parent = left_child = next_brother = prio_brother = NULL;
    p_surface = NULL;
    memset(&bgcolor, 0, sizeof(bgcolor));
    memset(&fncolor, 0, sizeof(fncolor));
    memset(&allocation, 0, sizeof(allocation));
    screen_x = screen_y = 0;
    level = 0;
    //flags = 0;
    //alpha = 0;
    //memset(&colorkey, 0, sizeof(SDL_Color));
    movement = NULL;
    effect = NULL;
    visible = true;
}

Widget::Widget()
{
    init();
}

Widget::~Widget()
{
    if (movement != NULL) {
        delete movement;
    }
    if (effect != NULL) {
        delete effect;
    }
}

/*
bool Widget::need_repaint()
{
    //TODO
    if (dirty) {
        return true;
    }
}
*/

void Widget::draw(SDL_Surface *screen)
{
    if (!visible) {
        return;
    }
    if (p_surface != NULL) {
        SDL_Rect dstrect;

        if (dirty || DirtyRect::is_all_dirty()) {
            SDL_SetClipRect(screen, NULL);
            dstrect.x = screen_x;
            dstrect.y = screen_y;
            if (effect != NULL) {
                effect->Blit(p_surface, NULL, screen, &dstrect);
            } else {
                SDL_BlitSurface(p_surface, NULL, screen, &dstrect);
            }
        } else {
            int rect_num = DirtyRect::get_number();
            int i;
            for (i=0; i<rect_num; i++) {
                SDL_Rect *p_rect = DirtyRect::get_rect(i);
                if (screen_x < p_rect->x + p_rect->w &&
                        screen_x + allocation.w > p_rect->x &&
                        screen_y < p_rect->y + p_rect->h &&
                        screen_y + allocation.h > p_rect->y) {
                    SDL_SetClipRect(screen, p_rect);
                    dstrect.x = screen_x;
                    dstrect.y = screen_y;
                    if (effect != NULL) {
                        effect->Blit(p_surface, NULL, screen, &dstrect);
                    } else {
                        SDL_BlitSurface(p_surface, NULL, screen, &dstrect);
                    }
                    //printf("%s,%d\n", objectID, i);
                }
            }
            // Cancel clip rect
            SDL_SetClipRect(screen, NULL);
        }
    }
    dirty = false;
}

void Widget::set_visible(bool v)
{
    visible = v;
    SDL_Rect dirty_rect;
    dirty_rect.x = screen_x;
    dirty_rect.y = screen_y;
    dirty_rect.w = allocation.w;
    dirty_rect.h = allocation.h;
    if (visible) {
        dirty = true;
    }
    DirtyRect::register_to(&dirty_rect);
}

bool Widget::get_visible()
{
    return visible;
}

void Widget::set_level(Sint16 val)
{
    if (level == val) {
        return;
    }
    level = val;
    Widget *ptr = NULL;
    if (next_brother != NULL && next_brother->get_level() < level) {
        ptr = next_brother;
        while (ptr != NULL) {
            if (ptr->get_level() >= level) {
                wtree_split_from_parent();
                ptr->wtree_add_brother_before(this);
                return;
            }
            ptr = ptr->next_brother;
        }
    }

    if (prio_brother != NULL && prio_brother->get_level() > level) {
        ptr = prio_brother;
        while (ptr != NULL) {
            if (ptr->get_level() <= level) {
                wtree_split_from_parent();
                ptr->wtree_add_brother_after(this);
                return;
            }
            ptr = ptr->prio_brother;
        }
    }
}

Sint16 Widget::get_level()
{
    return level;
}

void Widget::set_bgcolor(SDL_Color &c)
{
    bgcolor = c;
}

void Widget::set_fncolor(SDL_Color &c)
{
    fncolor = c;
}

void Widget::set_movement(Movement *m)
{
    if (movement != NULL) {
        delete movement;
    }
    movement = m;
}

void Widget::set_effect(Effect *e)
{
    if (effect != NULL) {
        delete effect;
    }
    effect = e;
}

void Widget::update()
{
    SDL_Rect dirty_rect;
    dirty_rect.x = screen_x;
    dirty_rect.y = screen_y;
    if (movement != NULL) {
        if (movement->update(allocation)) {
            dirty = true;
        }
    }
    if (effect != NULL) {
        if (effect->update()) {
            dirty = true;
        }
    }
    if (parent != NULL) {
        int old_x = screen_x;
        int old_y = screen_y;
        screen_x = parent->get_screen_x() + allocation.x;
        screen_y = parent->get_screen_y() + allocation.y;
        if (screen_x != old_x || screen_y != old_y) {
            dirty = true;
        }
    } else {
        screen_x = allocation.x;
        screen_y = allocation.y;
    }
    if (dirty && visible) {
        if (dirty_rect.x < screen_x) {
            dirty_rect.w = allocation.w + screen_x - dirty_rect.x;
        } else {
            dirty_rect.w = allocation.w + dirty_rect.x - screen_x;
        }
        if (dirty_rect.y < screen_y) {
            dirty_rect.h = allocation.h + screen_y - dirty_rect.y;
        } else {
            dirty_rect.h = allocation.h + dirty_rect.y - screen_y;
        }
        dirty_rect.x = std::min(dirty_rect.x, screen_x);
        dirty_rect.y = std::min(dirty_rect.y, screen_y);
        DirtyRect::register_to(&dirty_rect);
    }
}

SDL_Rect Widget::get_allocation()
{
    return allocation;
}

void Widget::set_allocation(SDL_Rect rect)
{
    set_allocation(rect.x, rect.y, rect.w, rect.h);
}

void Widget::set_allocation(Sint16 x, Sint16 y)
{
    set_allocation(x, y, allocation.w, allocation.h);
}

void Widget::set_allocation(Sint16 x, Sint16 y, Uint16 w, Uint16 h)
{
    set_movement(NULL);

    allocation.x = x;
    allocation.y = y;
    allocation.w = w;
    allocation.h = h;
/*
    int old_x = screen_x;
    int old_y = screen_y;
    if (parent != NULL) {
        screen_x = parent->get_screen_x() + allocation.x;
        screen_y = parent->get_screen_y() + allocation.y;
    } else {
        screen_x = allocation.x;
        screen_y = allocation.y;
    }
    if (visible && (screen_x != old_x || screen_y != old_y)) {
        dirty = true;
    }
*/
    if (visible) {
        dirty = true;
    }
    update();
}

Sint16 Widget::get_screen_x()
{
    return screen_x;
}


Sint16 Widget::get_screen_y()
{
    return screen_y;
}

void Widget::wtree_add_brother_before(Widget *node)
{
    node->next_brother = this;
    node->prio_brother = this->prio_brother;
    if (this->prio_brother != NULL) {
        this->prio_brother->next_brother = node;
    }
    this->prio_brother = node;
    node->parent = this->parent;
    if (this->parent != NULL) {
        if (this->parent->left_child == this) {
            this->parent->left_child = node;
        }
    }
}

void Widget::wtree_add_brother_after(Widget *node)
{
    node->next_brother = this->next_brother;
    node->prio_brother = this;
    if (this->next_brother != NULL) {
        this->next_brother->prio_brother = node;
    }
    this->next_brother = node;
    node->parent = this->parent;
}

void Widget::wtree_add_child(Widget *child)
{
    Widget *ptr = NULL;

    child->parent = this;
    if (this->left_child == NULL) {
        this->left_child = child;
        return;
    }

    //already have children, find a right place to insert (depend on level)
    ptr = this->left_child;
    while (child->get_level() >= ptr->get_level()) {
        if (ptr->next_brother != NULL) {
            ptr = ptr->next_brother;
        } else {
            break;
        }
    }
    if (child->get_level() < ptr->get_level()) {
        //add child before ptr
        ptr->wtree_add_brother_before(child);
    } else {
        //add child as the last child of the list
        ptr->wtree_add_brother_after(child);
    }
}

void Widget::wtree_split_from_parent()
{
    if (this == NULL) {
        return;
    }
    if (this->parent != NULL) {
        if (this->parent->left_child == this) {
            //this is the first child of its parent
            this->parent->left_child = this->next_brother;
        }
        this->parent = NULL;
    }
    if (this->next_brother != NULL) {
        this->next_brother->prio_brother = this->prio_brother;
        this->next_brother = NULL;
    }
    if (this->prio_brother != NULL) {
        this->prio_brother->next_brother = this->next_brother;
        this->prio_brother = NULL;
    }
}

#if 0
static inline void cb_remove_subtree_by_id(Widget *node, void *param, void *param2)
{
    misc::SimpleList<Widget *> *visitlist = (misc::SimpleList<Widget *> *)param;
    char *ID = (char *)param2;
    if (strcmp(node->objectID, ID) == 0) {
        visitlist->push_back(node);
    }
}
void wtree_remove_subtree_by_id(Widget *base, char *ID)
{
    misc::SimpleList<Widget *> matchedlist;
    Widget *n = NULL;

    if (base == NULL) {
        return;
    }
    wtree_levelorder_traversal(base, cb_remove_subtree_by_id, &matchedlist, ID);
    while (!matchedlist.empty()) {
        n = matchedlist.get_back();
        matchedlist.pop_back();
        wtree_remove_subtree(n);
    }
}
#endif
static inline void cb_break_tree(Widget *node, void *param, void *param2)
{
    misc::SimpleList<Widget *> *visitlist = (misc::SimpleList<Widget *> *)param;
    visitlist->push_back(node);
}

/**
 * split current tree from parent and break this tree
 */
void Widget::wtree_break_tree()
{
    misc::SimpleList<Widget *> visitlist;
    Widget *n = NULL;

    this->wtree_split_from_parent();
    wtree_levelorder_traversal(this, cb_break_tree, &visitlist, NULL);
    while (!visitlist.empty()) {
        n = visitlist.get_back();
        visitlist.pop_back();
        n->left_child = NULL;
        n->next_brother = NULL;
        n->prio_brother = NULL;
        n->parent = NULL;
    }
}

void Widget::wtree_levelorder_traversal(Widget *node,
        void (*callback)(Widget *node, void *param, void * param2),
        void *param, void *param2)
{
    Widget *n = NULL;
    misc::SimpleList<Widget *> nodelist;

    nodelist.push_front(node);
    while (!nodelist.empty()) {
        n = nodelist.get_front();
        nodelist.pop_front();
        if (callback != NULL) {
            //visit node
            callback(n, param, param2);
        }
        if (n->left_child != NULL) {
            nodelist.push_back(n->left_child);
        }
        if (n->next_brother != NULL) {
            nodelist.push_front(n->next_brother);
        }
    }
}

//TODO: not tested
void Widget::wtree_postorder_traversal(Widget *node,
        void (*callback)(Widget *node, void *param, void * param2),
        void *param, void *param2)
{
    bool backtracking = false;
    Widget *n = NULL;
    misc::SimpleList<Widget *> nodelist;

    nodelist.push_front(node);
    while (!nodelist.empty()) {
        n = nodelist.get_front();
        if (n->left_child != NULL && !backtracking) {
            nodelist.push_front(n->left_child);
            continue;
        }

        if (callback != NULL) {
            //visit node
            callback(n, param, param2);
        }
        nodelist.pop_front();

        if (n->next_brother != NULL) {
            nodelist.push_front(n->next_brother);
            backtracking = false;
            continue;
        }

        backtracking = true;
    }
}

}//end namespace animationagent
